﻿//  --------------------------------
//  Copyright (c) AW Systems, Inc. All rights reserved.
//  This source code is made available under the terms of the Microsoft Public License (Ms-PL)
//  http://www.codeplex.com/SLAM/license
//  ---------------------------------

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Web.Caching;
using System.Web.UI;
using Slam.Activation;
using Slam.Configuration;
using Slam.Events;
using Slam.Logging;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Web;

namespace Slam.Administration
{
	public partial class Synchronization : LayoutsPageBase, ICallbackEventHandler
	{
		private int timeout;
		private string slamFolder = "";

		protected override void OnInit(EventArgs e)
		{
			timeout = Server.ScriptTimeout;
			Server.ScriptTimeout = 8000;
			base.OnInit(e);
		}

		protected override void OnUnload(EventArgs e)
		{
			try
			{
				Server.ScriptTimeout = timeout;
			}
			catch { }
			base.OnUnload(e);
		}

		protected void Page_Load(object sender, EventArgs e)
		{
			string synchTypeName = "";
			string synchTypeId = "";
			string synchSite = "";
			string synchSiteCollection = "";

			if (Request.QueryString["typename"] != null)
			{
				synchTypeName = Request.QueryString["typename"];
			}

			if (Request.QueryString["typeid"] != null)
			{
				synchTypeId = Request.QueryString["typeid"];
			}

			if (Request.QueryString["site"] != null)
			{
				synchSite = HttpUtility.UrlDecode(Request.QueryString["site"]);
			}

			if (Request.QueryString["sitecollection"] != null)
			{
				synchSiteCollection = HttpUtility.UrlDecode(Request.QueryString["sitecollection"]);
			}

			//if under a site collection other than the root, doing a simple
			//redirect to the page will always redirect to the root, so the desired
			//location needs extracted from the RawUrl
			slamFolder = Request.RawUrl.Substring(0, Request.RawUrl.LastIndexOf("/") + 1);

			if (Request["__EVENTARGUMENT"] == "doredirect")
			{
				Response.Redirect(slamFolder + "Diagnostics.aspx");
			}

			if (!IsPostBack)
			{
				//Test if SLAM can clear instances, if it cannot go back to the Control Panel
				try
				{
					ComponentService.ClearInstances();
				}
				catch
				{
					Response.Redirect(slamFolder + "SLAMControlPanel.aspx");
				}

				hdnSessionKey.Value = DateTime.Now.Ticks.ToString();

				SPSite site = SPControl.GetContextSite(System.Web.HttpContext.Current);
				SynchronizationProcess process = new SynchronizationProcess(DoSynchronization);

				Cache.Insert("syncresult" + hdnSessionKey.Value.ToString(), process.BeginInvoke(site.Url, synchTypeName, synchTypeId, synchSiteCollection, synchSite, new AsyncCallback(CallBackMethod), null), null, DateTime.Now.AddHours(2), System.Web.Caching.Cache.NoSlidingExpiration, CacheItemPriority.Default, null);


				//Register javascript functions to handle synch progress updating
				//The process is started with a setTimeout to the intial GetStatus because
				//in after a app pool recycle in IE this process is not initiated every time
				//unless it waits for a moment, so the wait always occurs before the first
				//update.
				ClientScript.RegisterStartupScript(GetType(), "StartCallbackScript", @" 
						function GetStatus()
						{   
							" + ClientScript.GetCallbackEventReference(this, "", "ShowStatus", null) + @";
						}
						function ShowStatus(eventArgument,context)
						{   
							document.getElementById('progressArea').innerHTML = eventArgument;
							if(eventArgument == ""Finishing..."") 
							{
								setTimeout(""Finish()"", 1500);
							}
							else 
							{
								setTimeout(""GetStatus()"", 1000); 
							}
						}
						function Finish() 
						{   
							" + ClientScript.GetPostBackEventReference(this, "doredirect", false) + @";
						}
						setTimeout(""GetStatus()"", 300); ", true);
			}
			
		}

		private void CallBackMethod(IAsyncResult ar)
		{
			AsyncResult Result = (AsyncResult)ar;
			SynchronizationProcess process = (SynchronizationProcess)Result.AsyncDelegate;
			process.EndInvoke(Result);
		}

		private delegate void SynchronizationProcess(string siteCollectionUrl, string typeName, string typeId, string typeSiteCollection, string typeSite);

		private void DoSynchronization(string siteCollectionUrl, string typeName, string typeId, string typeSiteCollection, string typeSite)
		{	
			using (SPSite site = new SPSite(siteCollectionUrl))
			{
				using (SPWeb rootWeb = site.RootWeb)
				{
					//This is something of a hack, because this thread has no
					//HttpContext, the easiest way for default instances to use the correct
					//site collection is to set the SlamItemContext ListItem to a dummy
					//list item from the given site collection
					SPList firstList = rootWeb.Lists[0];
					SlamItemContext.ListItem = firstList.Items.Add();

					IConfigurationManager config = ComponentService.GetInstance<IConfigurationManager>();
					EventUtility eventUtility = new EventUtility();
					eventUtility.DisableEventHandlers();

					ILogger logger = ComponentService.GetInstance<ILogger>();
					TraceLevel currentTraceLevel = TraceLevel.Error;

					SqlLogger sqlLogger = logger is SqlLogger ? ((SqlLogger)logger) : null;
					if (sqlLogger != null)
					{
						currentTraceLevel = sqlLogger.Switch.Level;
						sqlLogger.Switch.Level = TraceLevel.Verbose;
					}
					logger.LogMessage("Information", "Data Synchronization initiated");
					if (sqlLogger != null)
					{
						sqlLogger.Switch.Level = currentTraceLevel;
					}

					string siteCollection = site.ServerRelativeUrl.TrimStart('/');
					DataMapping currentDataMapping = config.DataMapping;
					if (!String.IsNullOrEmpty(siteCollection) && config.DataMappings.ContainsKey(siteCollection))
					{
						currentDataMapping = config.DataMappings[siteCollection];
					}

					rootWeb.Dispose();
					SlamItemContext.ResetContext();

					int currentTypeNumber = 1;
					Cache.Insert("syncstatus" + hdnSessionKey.Value.ToString(), "Initiating...", null, DateTime.Now.AddHours(2), System.Web.Caching.Cache.NoSlidingExpiration, CacheItemPriority.Default, null);

					bool runTypeSubset = false;
					if (!String.IsNullOrEmpty(typeName) || !String.IsNullOrEmpty(typeId) || !String.IsNullOrEmpty(typeSite) || !String.IsNullOrEmpty(typeSiteCollection))
					{
						runTypeSubset = true;
					}

					List<ConfiguredType> typesToSynch = new List<ConfiguredType>();

					foreach (ConfiguredType type in currentDataMapping.GetAllTypes())
					{
						if (!runTypeSubset)
						{
							typesToSynch.Add(type);
						}
						else if (type.Name == typeName)
						{
							typesToSynch.Add(type);
						}
					}

					//To perform synchronization of all configued types, 2 passes are required.
					//First pass calls IDataSynchronizeTypeProcessor.Process for each type with PopulateDataCleanup option set to "false".
					
					int totalTypes = typesToSynch.Count;

					foreach (ConfiguredType type in typesToSynch)
					{
						string itemTypeName = type.Name;

						if (type is SqlConfiguredType)
						{
							SqlConfiguredType sqlType = (SqlConfiguredType)type;
							if (!String.IsNullOrEmpty(sqlType.Table))
							{
								itemTypeName = sqlType.Table;
							}
						}

						Cache["syncstatus" + hdnSessionKey.Value.ToString()] = currentTypeNumber.ToString() + " of " + totalTypes.ToString() + "<br />Processing " + itemTypeName + "...";

						try
						{
							IDataSynchronizeTypeProcessor processor = ComponentService.GetInstance<IDataSynchronizeTypeProcessor>(type.SiteCollection, type.Site, type.Id, itemTypeName);
							processor.Process(site.Url, new Dictionary<string, object>() { { "ConfigType", type }, { "CreateTable", true }, { "PopulateDataCleanup", false }, { "PopulateData", true } });
						}
						catch (Exception ex)
						{
							//Because this process is asynchronous, in case an unexpected exception is
							//thrown while processing this type, catch it and log it
							sqlLogger.LogMessage("Exception", ex.ToString());
						}
						if (sqlLogger != null)
						{
							sqlLogger.Switch.Level = TraceLevel.Verbose;
						}
						logger.LogMessage("Information", "Data Synchronization Processing: " + type.Name);
						if (sqlLogger != null)
						{
							sqlLogger.Switch.Level = currentTraceLevel;
						}
						currentTypeNumber++;
						Cache["syncstatus" + hdnSessionKey.Value.ToString()] = currentTypeNumber.ToString() + " of " + totalTypes.ToString();
					}

					//Second pass calls IDataSynchronizeTypeProcessor.Process for each type with PopulateDataCleanup option set to "true" but in reverse order because of association constraints.
					typesToSynch.Reverse();
					currentTypeNumber = 1;

					foreach (ConfiguredType type in typesToSynch)
					{
						string itemTypeName = type.Name;

						if (type is SqlConfiguredType)
						{
							SqlConfiguredType sqlType = (SqlConfiguredType)type;
							if (!String.IsNullOrEmpty(sqlType.Table))
							{
								itemTypeName = sqlType.Table;
							}
						}

						Cache["syncstatus" + hdnSessionKey.Value.ToString()] = currentTypeNumber.ToString() + " of " + totalTypes.ToString() + "<br />Cleanup " + itemTypeName + "...";

						try
						{
							IDataSynchronizeTypeProcessor processor = ComponentService.GetInstance<IDataSynchronizeTypeProcessor>(type.SiteCollection, type.Site, type.Id, itemTypeName);
							processor.Process(site.Url, new Dictionary<string, object>() { { "ConfigType", type }, { "CreateTable", false }, { "PopulateDataCleanup", true }, { "PopulateData", false } });
						}
						catch (Exception ex)
						{
							//Because this process is asynchronous, in case an unexpected exception is
							//thrown while processing this type, catch it and log it
							sqlLogger.LogMessage("Exception", ex.ToString());
						}
						if (sqlLogger != null)
						{
							sqlLogger.Switch.Level = TraceLevel.Verbose;
						}
						logger.LogMessage("Information", "Data Synchronization Cleanup: " + type.Name);
						if (sqlLogger != null)
						{
							sqlLogger.Switch.Level = currentTraceLevel;
						}
						currentTypeNumber++;
						Cache["syncstatus" + hdnSessionKey.Value.ToString()] = currentTypeNumber.ToString() + " of " + totalTypes.ToString();
					}

					if (sqlLogger != null)
					{
						sqlLogger.Switch.Level = TraceLevel.Verbose;
					}
					logger.LogMessage("Information", "Data Synchronization Complete");
					if (logger is SqlLogger)
					{
						((SqlLogger)logger).Switch.Level = currentTraceLevel;
					}

					eventUtility.EnableEventHandlers();
					Cache["syncstatus" + hdnSessionKey.Value.ToString()] = "Finishing...";
				}
			}
		}

		#region ICallbackEventHandler Members

		public string GetCallbackResult()
		{
			return Cache["syncstatus" + hdnSessionKey.Value.ToString()].ToString();
		}

		public void RaiseCallbackEvent(string eventArgument)
		{   
		}

		#endregion
	}
}
